{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-01-20T14:59:35.833272Z",
     "start_time": "2025-01-20T14:59:35.827669Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 19
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 数据准备",
   "id": "301fe71da4864f8d"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T14:59:35.860291Z",
     "start_time": "2025-01-20T14:59:35.845289Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torchvision import datasets\n",
    "from torchvision.transforms import ToTensor, Resize, Compose, ConvertImageDtype, Normalize\n",
    "\n",
    "from pathlib import Path\n",
    "\n",
    "DATA_DIR = Path(\"./archive/\")\n",
    "\n",
    "\n",
    "class MonkeyDataset(datasets.ImageFolder):\n",
    "    def __init__(self, mode, transform=None):\n",
    "        if mode == \"train\":\n",
    "            root = DATA_DIR / \"training\"\n",
    "        elif mode == \"val\":\n",
    "            root = DATA_DIR / \"validation\"\n",
    "        else:\n",
    "            raise ValueError(\"mode should be one of the following: train, val, but got {}\".format(mode))\n",
    "        super().__init__(root, transform)\n",
    "        self.imgs = self.samples\n",
    "        self.targets = [s[1] for s in self.samples]\n",
    "\n",
    "\n",
    "# resnet 要求的\n",
    "img_h, img_w = 224, 224\n",
    "transform = Compose([\n",
    "    Resize((img_h, img_w)),\n",
    "    ToTensor(),\n",
    "    Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),\n",
    "    ConvertImageDtype(torch.float),\n",
    "])\n",
    "\n",
    "train_ds = MonkeyDataset(\"train\", transform=transform)\n",
    "val_ds = MonkeyDataset(\"val\", transform=transform)\n",
    "\n",
    "print(\"load {} images from training dataset\".format(len(train_ds)))\n",
    "print(\"load {} images from validation dataset\".format(len(val_ds)))"
   ],
   "id": "a07d578b12671072",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "load 1097 images from training dataset\n",
      "load 272 images from validation dataset\n"
     ]
    }
   ],
   "execution_count": 20
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T14:59:35.896591Z",
     "start_time": "2025-01-20T14:59:35.892315Z"
    }
   },
   "cell_type": "code",
   "source": [
    "import torch.nn as nn\n",
    "from torch.utils.data.dataloader import DataLoader\n",
    "\n",
    "batch_size = 16\n",
    "# 从数据集到dataloader\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(val_ds, batch_size=batch_size, shuffle=False)"
   ],
   "id": "65b0852b81de3544",
   "outputs": [],
   "execution_count": 21
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T14:59:36.153066Z",
     "start_time": "2025-01-20T14:59:35.909598Z"
    }
   },
   "cell_type": "code",
   "source": [
    "for imgs, labels in train_loader:\n",
    "    print(imgs.shape)\n",
    "    print(labels.shape)\n",
    "    break"
   ],
   "id": "8827a2c5c043718e",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([16, 3, 224, 224])\n",
      "torch.Size([16])\n"
     ]
    }
   ],
   "execution_count": 22
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 定义模型",
   "id": "95425eaeb7222f65"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T14:59:36.545527Z",
     "start_time": "2025-01-20T14:59:36.155068Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torchvision.models import resnet50\n",
    "\n",
    "\n",
    "# 加载resnet50模型以及预训练权重，冻结除最后一层外所有层的权重，去除最后一层并添加自定义分类层\n",
    "class ResNet50(nn.Module):\n",
    "    # fronzen: 是否冻结除最后一层外所有层的权重\n",
    "    def __init__(self, num_classes=10, frozen=True):\n",
    "        super().__init__()\n",
    "        # 下载预训练权重\n",
    "        self.model = resnet50(weights=\"IMAGENET1K_V2\", )  # 这里的weights参数可以选择\"IMAGENET1K_V2\"或None，None表示随机初始化\n",
    "        # 冻结前面的层\n",
    "        if frozen:\n",
    "            for param in self.model.parameters():\n",
    "                param.requires_grad = False  # 冻结权重\n",
    "        # 解冻指定层\n",
    "        # for param in self.model.layer4.parameters():\n",
    "        #     param.requires_grad = True  # 解冻 layer4\n",
    "        for name, param in self.model.named_parameters():\n",
    "            if name == \"layer4.2.conv3.weight\":  # 指定层名字\n",
    "                param.requires_grad = True\n",
    "                # 添加自定义分类层\n",
    "        print(self.model.fc.in_features)  # 打印resnet50的最后一层的输入通道数\n",
    "        print(self.model.fc.out_features)  # 打印resnet50的最后一层的输出通道数 1000\n",
    "        # 自定义分类层,把resnet50的最后一层改为num_classes个输出\n",
    "        self.model.fc = nn.Linear(self.model.fc.in_features, num_classes)\n",
    "\n",
    "    def forward(self, x):\n",
    "        return self.model(x)\n",
    "    \n",
    "    \n",
    "for idx, (key, value) in enumerate(ResNet50().named_parameters()):\n",
    "    print(f\"{key:^40}paramerters num: {np.prod(value.shape)}\")"
   ],
   "id": "adf160bcb04b6b6f",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2048\n",
      "1000\n",
      "           model.conv1.weight           paramerters num: 9408\n",
      "            model.bn1.weight            paramerters num: 64\n",
      "             model.bn1.bias             paramerters num: 64\n",
      "      model.layer1.0.conv1.weight       paramerters num: 4096\n",
      "       model.layer1.0.bn1.weight        paramerters num: 64\n",
      "        model.layer1.0.bn1.bias         paramerters num: 64\n",
      "      model.layer1.0.conv2.weight       paramerters num: 36864\n",
      "       model.layer1.0.bn2.weight        paramerters num: 64\n",
      "        model.layer1.0.bn2.bias         paramerters num: 64\n",
      "      model.layer1.0.conv3.weight       paramerters num: 16384\n",
      "       model.layer1.0.bn3.weight        paramerters num: 256\n",
      "        model.layer1.0.bn3.bias         paramerters num: 256\n",
      "   model.layer1.0.downsample.0.weight   paramerters num: 16384\n",
      "   model.layer1.0.downsample.1.weight   paramerters num: 256\n",
      "    model.layer1.0.downsample.1.bias    paramerters num: 256\n",
      "      model.layer1.1.conv1.weight       paramerters num: 16384\n",
      "       model.layer1.1.bn1.weight        paramerters num: 64\n",
      "        model.layer1.1.bn1.bias         paramerters num: 64\n",
      "      model.layer1.1.conv2.weight       paramerters num: 36864\n",
      "       model.layer1.1.bn2.weight        paramerters num: 64\n",
      "        model.layer1.1.bn2.bias         paramerters num: 64\n",
      "      model.layer1.1.conv3.weight       paramerters num: 16384\n",
      "       model.layer1.1.bn3.weight        paramerters num: 256\n",
      "        model.layer1.1.bn3.bias         paramerters num: 256\n",
      "      model.layer1.2.conv1.weight       paramerters num: 16384\n",
      "       model.layer1.2.bn1.weight        paramerters num: 64\n",
      "        model.layer1.2.bn1.bias         paramerters num: 64\n",
      "      model.layer1.2.conv2.weight       paramerters num: 36864\n",
      "       model.layer1.2.bn2.weight        paramerters num: 64\n",
      "        model.layer1.2.bn2.bias         paramerters num: 64\n",
      "      model.layer1.2.conv3.weight       paramerters num: 16384\n",
      "       model.layer1.2.bn3.weight        paramerters num: 256\n",
      "        model.layer1.2.bn3.bias         paramerters num: 256\n",
      "      model.layer2.0.conv1.weight       paramerters num: 32768\n",
      "       model.layer2.0.bn1.weight        paramerters num: 128\n",
      "        model.layer2.0.bn1.bias         paramerters num: 128\n",
      "      model.layer2.0.conv2.weight       paramerters num: 147456\n",
      "       model.layer2.0.bn2.weight        paramerters num: 128\n",
      "        model.layer2.0.bn2.bias         paramerters num: 128\n",
      "      model.layer2.0.conv3.weight       paramerters num: 65536\n",
      "       model.layer2.0.bn3.weight        paramerters num: 512\n",
      "        model.layer2.0.bn3.bias         paramerters num: 512\n",
      "   model.layer2.0.downsample.0.weight   paramerters num: 131072\n",
      "   model.layer2.0.downsample.1.weight   paramerters num: 512\n",
      "    model.layer2.0.downsample.1.bias    paramerters num: 512\n",
      "      model.layer2.1.conv1.weight       paramerters num: 65536\n",
      "       model.layer2.1.bn1.weight        paramerters num: 128\n",
      "        model.layer2.1.bn1.bias         paramerters num: 128\n",
      "      model.layer2.1.conv2.weight       paramerters num: 147456\n",
      "       model.layer2.1.bn2.weight        paramerters num: 128\n",
      "        model.layer2.1.bn2.bias         paramerters num: 128\n",
      "      model.layer2.1.conv3.weight       paramerters num: 65536\n",
      "       model.layer2.1.bn3.weight        paramerters num: 512\n",
      "        model.layer2.1.bn3.bias         paramerters num: 512\n",
      "      model.layer2.2.conv1.weight       paramerters num: 65536\n",
      "       model.layer2.2.bn1.weight        paramerters num: 128\n",
      "        model.layer2.2.bn1.bias         paramerters num: 128\n",
      "      model.layer2.2.conv2.weight       paramerters num: 147456\n",
      "       model.layer2.2.bn2.weight        paramerters num: 128\n",
      "        model.layer2.2.bn2.bias         paramerters num: 128\n",
      "      model.layer2.2.conv3.weight       paramerters num: 65536\n",
      "       model.layer2.2.bn3.weight        paramerters num: 512\n",
      "        model.layer2.2.bn3.bias         paramerters num: 512\n",
      "      model.layer2.3.conv1.weight       paramerters num: 65536\n",
      "       model.layer2.3.bn1.weight        paramerters num: 128\n",
      "        model.layer2.3.bn1.bias         paramerters num: 128\n",
      "      model.layer2.3.conv2.weight       paramerters num: 147456\n",
      "       model.layer2.3.bn2.weight        paramerters num: 128\n",
      "        model.layer2.3.bn2.bias         paramerters num: 128\n",
      "      model.layer2.3.conv3.weight       paramerters num: 65536\n",
      "       model.layer2.3.bn3.weight        paramerters num: 512\n",
      "        model.layer2.3.bn3.bias         paramerters num: 512\n",
      "      model.layer3.0.conv1.weight       paramerters num: 131072\n",
      "       model.layer3.0.bn1.weight        paramerters num: 256\n",
      "        model.layer3.0.bn1.bias         paramerters num: 256\n",
      "      model.layer3.0.conv2.weight       paramerters num: 589824\n",
      "       model.layer3.0.bn2.weight        paramerters num: 256\n",
      "        model.layer3.0.bn2.bias         paramerters num: 256\n",
      "      model.layer3.0.conv3.weight       paramerters num: 262144\n",
      "       model.layer3.0.bn3.weight        paramerters num: 1024\n",
      "        model.layer3.0.bn3.bias         paramerters num: 1024\n",
      "   model.layer3.0.downsample.0.weight   paramerters num: 524288\n",
      "   model.layer3.0.downsample.1.weight   paramerters num: 1024\n",
      "    model.layer3.0.downsample.1.bias    paramerters num: 1024\n",
      "      model.layer3.1.conv1.weight       paramerters num: 262144\n",
      "       model.layer3.1.bn1.weight        paramerters num: 256\n",
      "        model.layer3.1.bn1.bias         paramerters num: 256\n",
      "      model.layer3.1.conv2.weight       paramerters num: 589824\n",
      "       model.layer3.1.bn2.weight        paramerters num: 256\n",
      "        model.layer3.1.bn2.bias         paramerters num: 256\n",
      "      model.layer3.1.conv3.weight       paramerters num: 262144\n",
      "       model.layer3.1.bn3.weight        paramerters num: 1024\n",
      "        model.layer3.1.bn3.bias         paramerters num: 1024\n",
      "      model.layer3.2.conv1.weight       paramerters num: 262144\n",
      "       model.layer3.2.bn1.weight        paramerters num: 256\n",
      "        model.layer3.2.bn1.bias         paramerters num: 256\n",
      "      model.layer3.2.conv2.weight       paramerters num: 589824\n",
      "       model.layer3.2.bn2.weight        paramerters num: 256\n",
      "        model.layer3.2.bn2.bias         paramerters num: 256\n",
      "      model.layer3.2.conv3.weight       paramerters num: 262144\n",
      "       model.layer3.2.bn3.weight        paramerters num: 1024\n",
      "        model.layer3.2.bn3.bias         paramerters num: 1024\n",
      "      model.layer3.3.conv1.weight       paramerters num: 262144\n",
      "       model.layer3.3.bn1.weight        paramerters num: 256\n",
      "        model.layer3.3.bn1.bias         paramerters num: 256\n",
      "      model.layer3.3.conv2.weight       paramerters num: 589824\n",
      "       model.layer3.3.bn2.weight        paramerters num: 256\n",
      "        model.layer3.3.bn2.bias         paramerters num: 256\n",
      "      model.layer3.3.conv3.weight       paramerters num: 262144\n",
      "       model.layer3.3.bn3.weight        paramerters num: 1024\n",
      "        model.layer3.3.bn3.bias         paramerters num: 1024\n",
      "      model.layer3.4.conv1.weight       paramerters num: 262144\n",
      "       model.layer3.4.bn1.weight        paramerters num: 256\n",
      "        model.layer3.4.bn1.bias         paramerters num: 256\n",
      "      model.layer3.4.conv2.weight       paramerters num: 589824\n",
      "       model.layer3.4.bn2.weight        paramerters num: 256\n",
      "        model.layer3.4.bn2.bias         paramerters num: 256\n",
      "      model.layer3.4.conv3.weight       paramerters num: 262144\n",
      "       model.layer3.4.bn3.weight        paramerters num: 1024\n",
      "        model.layer3.4.bn3.bias         paramerters num: 1024\n",
      "      model.layer3.5.conv1.weight       paramerters num: 262144\n",
      "       model.layer3.5.bn1.weight        paramerters num: 256\n",
      "        model.layer3.5.bn1.bias         paramerters num: 256\n",
      "      model.layer3.5.conv2.weight       paramerters num: 589824\n",
      "       model.layer3.5.bn2.weight        paramerters num: 256\n",
      "        model.layer3.5.bn2.bias         paramerters num: 256\n",
      "      model.layer3.5.conv3.weight       paramerters num: 262144\n",
      "       model.layer3.5.bn3.weight        paramerters num: 1024\n",
      "        model.layer3.5.bn3.bias         paramerters num: 1024\n",
      "      model.layer4.0.conv1.weight       paramerters num: 524288\n",
      "       model.layer4.0.bn1.weight        paramerters num: 512\n",
      "        model.layer4.0.bn1.bias         paramerters num: 512\n",
      "      model.layer4.0.conv2.weight       paramerters num: 2359296\n",
      "       model.layer4.0.bn2.weight        paramerters num: 512\n",
      "        model.layer4.0.bn2.bias         paramerters num: 512\n",
      "      model.layer4.0.conv3.weight       paramerters num: 1048576\n",
      "       model.layer4.0.bn3.weight        paramerters num: 2048\n",
      "        model.layer4.0.bn3.bias         paramerters num: 2048\n",
      "   model.layer4.0.downsample.0.weight   paramerters num: 2097152\n",
      "   model.layer4.0.downsample.1.weight   paramerters num: 2048\n",
      "    model.layer4.0.downsample.1.bias    paramerters num: 2048\n",
      "      model.layer4.1.conv1.weight       paramerters num: 1048576\n",
      "       model.layer4.1.bn1.weight        paramerters num: 512\n",
      "        model.layer4.1.bn1.bias         paramerters num: 512\n",
      "      model.layer4.1.conv2.weight       paramerters num: 2359296\n",
      "       model.layer4.1.bn2.weight        paramerters num: 512\n",
      "        model.layer4.1.bn2.bias         paramerters num: 512\n",
      "      model.layer4.1.conv3.weight       paramerters num: 1048576\n",
      "       model.layer4.1.bn3.weight        paramerters num: 2048\n",
      "        model.layer4.1.bn3.bias         paramerters num: 2048\n",
      "      model.layer4.2.conv1.weight       paramerters num: 1048576\n",
      "       model.layer4.2.bn1.weight        paramerters num: 512\n",
      "        model.layer4.2.bn1.bias         paramerters num: 512\n",
      "      model.layer4.2.conv2.weight       paramerters num: 2359296\n",
      "       model.layer4.2.bn2.weight        paramerters num: 512\n",
      "        model.layer4.2.bn2.bias         paramerters num: 512\n",
      "      model.layer4.2.conv3.weight       paramerters num: 1048576\n",
      "       model.layer4.2.bn3.weight        paramerters num: 2048\n",
      "        model.layer4.2.bn3.bias         paramerters num: 2048\n",
      "            model.fc.weight             paramerters num: 20480\n",
      "             model.fc.bias              paramerters num: 10\n"
     ]
    }
   ],
   "execution_count": 23
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T14:59:36.859461Z",
     "start_time": "2025-01-20T14:59:36.546530Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model = ResNet50(num_classes=10, frozen=True)\n",
    "def count_parameters(model): #计算模型总参数量\n",
    "    return sum(p.numel() for p in model.parameters() if p.requires_grad)\n",
    "count_parameters(model)"
   ],
   "id": "19b71ddc4ed3ee7d",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2048\n",
      "1000\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "1069066"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 24
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 训练模型",
   "id": "1114bace51969e52"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T14:59:36.866227Z",
     "start_time": "2025-01-20T14:59:36.861462Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "@torch.no_grad()  # 装饰器，禁止梯度计算\n",
    "def evaluate(model, data_loader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in data_loader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "\n",
    "        # 前向传播\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)  # 验证集损失\n",
    "        # tensor.item() 获取tensor的数值，loss是只有一个元素的tensor\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "        # 预测\n",
    "        preds = logits.argmax(axis=-1)  # 预测类别\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())  # tensor转numpy，再转list\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "\n",
    "    acc = accuracy_score(label_list, pred_list)  # 计算准确率\n",
    "    return np.mean(loss_list), acc  # # 返回验证集平均损失和准确率"
   ],
   "id": "17516956dbbf46b2",
   "outputs": [],
   "execution_count": 25
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T14:59:36.873285Z",
     "start_time": "2025-01-20T14:59:36.867228Z"
    }
   },
   "cell_type": "code",
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=500, save_best_only=True):\n",
    "        self.save_dir = save_dir  # 保存路径\n",
    "        self.save_step = save_step  # 保存步数\n",
    "        self.save_best_only = save_best_only  # 是否只保存最好的模型\n",
    "        self.best_metric = -1  # 最好的指标，指标不可能为负数，所以初始化为-1\n",
    "        # 创建保存路径\n",
    "        if not os.path.exists(self.save_dir):  # 如果不存在保存路径，则创建\n",
    "            os.makedirs(self.save_dir)\n",
    "\n",
    "    # 对象被调用时：当你将对象像函数一样调用时，Python 会自动调用 __call__ 方法。\n",
    "    # state_dict() 返回模型参数的字典，包括模型参数和优化器参数\n",
    "    # metric 是指标，可以是验证集的准确率，也可以是其他指标\n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return  # 不是保存步数，则直接返回\n",
    "\n",
    "        if self.save_best_only:\n",
    "            assert metric is not None  # 必须传入metric\n",
    "            if metric >= self.best_metric:  # 如果当前指标大于最好的指标\n",
    "                # save checkpoint\n",
    "                # 保存最好的模型，覆盖之前的模型，不保存step，只保存state_dict，即模型参数，不保存优化器参数\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"05_monkey_resnet50_best.ckpt\"))\n",
    "                self.best_metric = metric  # 更新最好的指标\n",
    "        else:\n",
    "            # 保存模型\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "            # 保存每个step的模型，不覆盖之前的模型，保存step，保存state_dict，即模型参数，不保存优化器参数"
   ],
   "id": "32dbea1b407aab4e",
   "outputs": [],
   "execution_count": 26
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T14:59:36.879165Z",
     "start_time": "2025-01-20T14:59:36.874287Z"
    }
   },
   "cell_type": "code",
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        self.patience = patience  # 多少个step没有提升就停止训练\n",
    "        self.min_delta = min_delta  # 最小的提升幅度\n",
    "        self.best_metric = -1  # 记录的最好的指标\n",
    "        self.counter = 0  # 计数器，记录连续多少个step没有提升\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:  # 如果指标提升了\n",
    "            self.best_metric = metric  # 更新最好的指标\n",
    "            self.counter = 0  # 计数器清零\n",
    "        else:\n",
    "            self.counter += 1  # 计数器加一\n",
    "\n",
    "    @property  # 使用@property装饰器，使得 对象.early_stop可以调用，不需要()\n",
    "    def early_stop(self):\n",
    "        # 如果计数器大于等于patience，则返回True，停止训练\n",
    "        return self.counter >= self.patience"
   ],
   "id": "b3e8a82533f3db61",
   "outputs": [],
   "execution_count": 27
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T14:59:36.887944Z",
     "start_time": "2025-01-20T14:59:36.880166Z"
    }
   },
   "cell_type": "code",
   "source": [
    "def training(model,\n",
    "             train_loader,\n",
    "             val_loader,\n",
    "             epoch,\n",
    "             loss_fct,\n",
    "             optimizer,\n",
    "             save_ckpt_callback=None,\n",
    "             early_stop_callback=None,\n",
    "             eval_step=500,\n",
    "             ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0  # 全局步数\n",
    "    model.train()  # 训练模式\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "\n",
    "                # 前向传播\n",
    "                logits = model(datas)\n",
    "                loss = loss_fct(logits, labels)  # 训练集损失\n",
    "                preds = logits.argmax(axis=-1)  # 预测类别\n",
    "\n",
    "                # 反向传播\n",
    "                optimizer.zero_grad()  # 梯度清零\n",
    "                loss.backward()  # 反向传播\n",
    "                optimizer.step()  # 优化器更新参数\n",
    "\n",
    "                # 计算准确率\n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())\n",
    "                loss = loss.cpu().item()\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss,\n",
    "                    \"acc\": acc,\n",
    "                    \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # 评估\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()  # 评估模式\n",
    "                    # 验证集损失和准确率\n",
    "                    val_loss, val_acc = evaluate(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss,\n",
    "                        \"acc\": val_acc,\n",
    "                        \"step\": global_step\n",
    "                    })\n",
    "                    model.train()  # 训练模式\n",
    "\n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        # model.state_dict() 返回模型参数的字典，包括模型参数和优化器参数\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), val_acc)\n",
    "                        # 保存最好的模型，覆盖之前的模型，保存step，保存state_dict,通过metric判断是否保存最好的模型\n",
    "\n",
    "                    # 3. 早停 early stopping\n",
    "                    if early_stop_callback is not None:\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        early_stop_callback(val_acc)\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict  # 早停，返回记录字典 record_dict\n",
    "\n",
    "                # 更新进度条和全局步数\n",
    "                pbar.update(1)  # 更新进度条\n",
    "                global_step += 1  # 全局步数加一\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict  # 训练结束，返回记录字典 record_dict"
   ],
   "id": "98eb670284913f62",
   "outputs": [],
   "execution_count": 28
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T14:59:37.243104Z",
     "start_time": "2025-01-20T14:59:36.889952Z"
    }
   },
   "cell_type": "code",
   "source": [
    "epoch = 100\n",
    "\n",
    "model = ResNet50(num_classes=len(train_ds.classes))  # 定义模型\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "\n",
    "# 2. 定义优化器 采用SGD优化器\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.0)\n",
    "\n",
    "# 3.save model checkpoint\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(save_dir=\"checkpoints\", save_step=len(train_loader), save_best_only=True)\n",
    "\n",
    "# 4. early stopping\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=0.01)"
   ],
   "id": "b398a93f6797ab28",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2048\n",
      "1000\n"
     ]
    }
   ],
   "execution_count": 29
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T15:00:30.445872Z",
     "start_time": "2025-01-20T14:59:37.244106Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model = model.to(device)  # 将模型移到GPU上\n",
    "\n",
    "# 训练过程\n",
    "record_dict = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    val_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    ")"
   ],
   "id": "f14dfe5e75ac3f87",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/6900 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "c7fd30b0f00b471fa27c79035a63b5bf"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "ename": "KeyboardInterrupt",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001B[1;31m---------------------------------------------------------------------------\u001B[0m",
      "\u001B[1;31mKeyboardInterrupt\u001B[0m                         Traceback (most recent call last)",
      "Cell \u001B[1;32mIn[30], line 4\u001B[0m\n\u001B[0;32m      1\u001B[0m model \u001B[38;5;241m=\u001B[39m model\u001B[38;5;241m.\u001B[39mto(device)  \u001B[38;5;66;03m# 将模型移到GPU上\u001B[39;00m\n\u001B[0;32m      3\u001B[0m \u001B[38;5;66;03m# 训练过程\u001B[39;00m\n\u001B[1;32m----> 4\u001B[0m record_dict \u001B[38;5;241m=\u001B[39m \u001B[43mtraining\u001B[49m\u001B[43m(\u001B[49m\n\u001B[0;32m      5\u001B[0m \u001B[43m    \u001B[49m\u001B[43mmodel\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m      6\u001B[0m \u001B[43m    \u001B[49m\u001B[43mtrain_loader\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m      7\u001B[0m \u001B[43m    \u001B[49m\u001B[43mval_loader\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m      8\u001B[0m \u001B[43m    \u001B[49m\u001B[43mepoch\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m      9\u001B[0m \u001B[43m    \u001B[49m\u001B[43mloss_fct\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m     10\u001B[0m \u001B[43m    \u001B[49m\u001B[43moptimizer\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m     11\u001B[0m \u001B[43m    \u001B[49m\u001B[43msave_ckpt_callback\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43msave_ckpt_callback\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m     12\u001B[0m \u001B[43m    \u001B[49m\u001B[43mearly_stop_callback\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mearly_stop_callback\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m     13\u001B[0m \u001B[43m    \u001B[49m\u001B[43meval_step\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43mlen\u001B[39;49m\u001B[43m(\u001B[49m\u001B[43mtrain_loader\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m     14\u001B[0m \u001B[43m)\u001B[49m\n",
      "Cell \u001B[1;32mIn[28], line 25\u001B[0m, in \u001B[0;36mtraining\u001B[1;34m(model, train_loader, val_loader, epoch, loss_fct, optimizer, save_ckpt_callback, early_stop_callback, eval_step)\u001B[0m\n\u001B[0;32m     22\u001B[0m labels \u001B[38;5;241m=\u001B[39m labels\u001B[38;5;241m.\u001B[39mto(device)\n\u001B[0;32m     24\u001B[0m \u001B[38;5;66;03m# 前向传播\u001B[39;00m\n\u001B[1;32m---> 25\u001B[0m logits \u001B[38;5;241m=\u001B[39m \u001B[43mmodel\u001B[49m\u001B[43m(\u001B[49m\u001B[43mdatas\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m     26\u001B[0m loss \u001B[38;5;241m=\u001B[39m loss_fct(logits, labels)  \u001B[38;5;66;03m# 训练集损失\u001B[39;00m\n\u001B[0;32m     27\u001B[0m preds \u001B[38;5;241m=\u001B[39m logits\u001B[38;5;241m.\u001B[39margmax(axis\u001B[38;5;241m=\u001B[39m\u001B[38;5;241m-\u001B[39m\u001B[38;5;241m1\u001B[39m)  \u001B[38;5;66;03m# 预测类别\u001B[39;00m\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1736\u001B[0m, in \u001B[0;36mModule._wrapped_call_impl\u001B[1;34m(self, *args, **kwargs)\u001B[0m\n\u001B[0;32m   1734\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_compiled_call_impl(\u001B[38;5;241m*\u001B[39margs, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs)  \u001B[38;5;66;03m# type: ignore[misc]\u001B[39;00m\n\u001B[0;32m   1735\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[1;32m-> 1736\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_call_impl\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1747\u001B[0m, in \u001B[0;36mModule._call_impl\u001B[1;34m(self, *args, **kwargs)\u001B[0m\n\u001B[0;32m   1742\u001B[0m \u001B[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001B[39;00m\n\u001B[0;32m   1743\u001B[0m \u001B[38;5;66;03m# this function, and just call forward.\u001B[39;00m\n\u001B[0;32m   1744\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m (\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_backward_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_backward_pre_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_forward_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_forward_pre_hooks\n\u001B[0;32m   1745\u001B[0m         \u001B[38;5;129;01mor\u001B[39;00m _global_backward_pre_hooks \u001B[38;5;129;01mor\u001B[39;00m _global_backward_hooks\n\u001B[0;32m   1746\u001B[0m         \u001B[38;5;129;01mor\u001B[39;00m _global_forward_hooks \u001B[38;5;129;01mor\u001B[39;00m _global_forward_pre_hooks):\n\u001B[1;32m-> 1747\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mforward_call\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m   1749\u001B[0m result \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;01mNone\u001B[39;00m\n\u001B[0;32m   1750\u001B[0m called_always_called_hooks \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mset\u001B[39m()\n",
      "Cell \u001B[1;32mIn[23], line 28\u001B[0m, in \u001B[0;36mResNet50.forward\u001B[1;34m(self, x)\u001B[0m\n\u001B[0;32m     27\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mforward\u001B[39m(\u001B[38;5;28mself\u001B[39m, x):\n\u001B[1;32m---> 28\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mmodel\u001B[49m\u001B[43m(\u001B[49m\u001B[43mx\u001B[49m\u001B[43m)\u001B[49m\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1736\u001B[0m, in \u001B[0;36mModule._wrapped_call_impl\u001B[1;34m(self, *args, **kwargs)\u001B[0m\n\u001B[0;32m   1734\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_compiled_call_impl(\u001B[38;5;241m*\u001B[39margs, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs)  \u001B[38;5;66;03m# type: ignore[misc]\u001B[39;00m\n\u001B[0;32m   1735\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[1;32m-> 1736\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_call_impl\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1747\u001B[0m, in \u001B[0;36mModule._call_impl\u001B[1;34m(self, *args, **kwargs)\u001B[0m\n\u001B[0;32m   1742\u001B[0m \u001B[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001B[39;00m\n\u001B[0;32m   1743\u001B[0m \u001B[38;5;66;03m# this function, and just call forward.\u001B[39;00m\n\u001B[0;32m   1744\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m (\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_backward_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_backward_pre_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_forward_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_forward_pre_hooks\n\u001B[0;32m   1745\u001B[0m         \u001B[38;5;129;01mor\u001B[39;00m _global_backward_pre_hooks \u001B[38;5;129;01mor\u001B[39;00m _global_backward_hooks\n\u001B[0;32m   1746\u001B[0m         \u001B[38;5;129;01mor\u001B[39;00m _global_forward_hooks \u001B[38;5;129;01mor\u001B[39;00m _global_forward_pre_hooks):\n\u001B[1;32m-> 1747\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mforward_call\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m   1749\u001B[0m result \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;01mNone\u001B[39;00m\n\u001B[0;32m   1750\u001B[0m called_always_called_hooks \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mset\u001B[39m()\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torchvision\\models\\resnet.py:285\u001B[0m, in \u001B[0;36mResNet.forward\u001B[1;34m(self, x)\u001B[0m\n\u001B[0;32m    284\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mforward\u001B[39m(\u001B[38;5;28mself\u001B[39m, x: Tensor) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m Tensor:\n\u001B[1;32m--> 285\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_forward_impl\u001B[49m\u001B[43m(\u001B[49m\u001B[43mx\u001B[49m\u001B[43m)\u001B[49m\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torchvision\\models\\resnet.py:273\u001B[0m, in \u001B[0;36mResNet._forward_impl\u001B[1;34m(self, x)\u001B[0m\n\u001B[0;32m    270\u001B[0m x \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mrelu(x)\n\u001B[0;32m    271\u001B[0m x \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mmaxpool(x)\n\u001B[1;32m--> 273\u001B[0m x \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mlayer1\u001B[49m\u001B[43m(\u001B[49m\u001B[43mx\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m    274\u001B[0m x \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mlayer2(x)\n\u001B[0;32m    275\u001B[0m x \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mlayer3(x)\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1736\u001B[0m, in \u001B[0;36mModule._wrapped_call_impl\u001B[1;34m(self, *args, **kwargs)\u001B[0m\n\u001B[0;32m   1734\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_compiled_call_impl(\u001B[38;5;241m*\u001B[39margs, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs)  \u001B[38;5;66;03m# type: ignore[misc]\u001B[39;00m\n\u001B[0;32m   1735\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[1;32m-> 1736\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_call_impl\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1747\u001B[0m, in \u001B[0;36mModule._call_impl\u001B[1;34m(self, *args, **kwargs)\u001B[0m\n\u001B[0;32m   1742\u001B[0m \u001B[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001B[39;00m\n\u001B[0;32m   1743\u001B[0m \u001B[38;5;66;03m# this function, and just call forward.\u001B[39;00m\n\u001B[0;32m   1744\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m (\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_backward_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_backward_pre_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_forward_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_forward_pre_hooks\n\u001B[0;32m   1745\u001B[0m         \u001B[38;5;129;01mor\u001B[39;00m _global_backward_pre_hooks \u001B[38;5;129;01mor\u001B[39;00m _global_backward_hooks\n\u001B[0;32m   1746\u001B[0m         \u001B[38;5;129;01mor\u001B[39;00m _global_forward_hooks \u001B[38;5;129;01mor\u001B[39;00m _global_forward_pre_hooks):\n\u001B[1;32m-> 1747\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mforward_call\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m   1749\u001B[0m result \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;01mNone\u001B[39;00m\n\u001B[0;32m   1750\u001B[0m called_always_called_hooks \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mset\u001B[39m()\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\container.py:250\u001B[0m, in \u001B[0;36mSequential.forward\u001B[1;34m(self, input)\u001B[0m\n\u001B[0;32m    248\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mforward\u001B[39m(\u001B[38;5;28mself\u001B[39m, \u001B[38;5;28minput\u001B[39m):\n\u001B[0;32m    249\u001B[0m     \u001B[38;5;28;01mfor\u001B[39;00m module \u001B[38;5;129;01min\u001B[39;00m \u001B[38;5;28mself\u001B[39m:\n\u001B[1;32m--> 250\u001B[0m         \u001B[38;5;28minput\u001B[39m \u001B[38;5;241m=\u001B[39m \u001B[43mmodule\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43minput\u001B[39;49m\u001B[43m)\u001B[49m\n\u001B[0;32m    251\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28minput\u001B[39m\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1736\u001B[0m, in \u001B[0;36mModule._wrapped_call_impl\u001B[1;34m(self, *args, **kwargs)\u001B[0m\n\u001B[0;32m   1734\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_compiled_call_impl(\u001B[38;5;241m*\u001B[39margs, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs)  \u001B[38;5;66;03m# type: ignore[misc]\u001B[39;00m\n\u001B[0;32m   1735\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[1;32m-> 1736\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_call_impl\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1747\u001B[0m, in \u001B[0;36mModule._call_impl\u001B[1;34m(self, *args, **kwargs)\u001B[0m\n\u001B[0;32m   1742\u001B[0m \u001B[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001B[39;00m\n\u001B[0;32m   1743\u001B[0m \u001B[38;5;66;03m# this function, and just call forward.\u001B[39;00m\n\u001B[0;32m   1744\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m (\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_backward_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_backward_pre_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_forward_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_forward_pre_hooks\n\u001B[0;32m   1745\u001B[0m         \u001B[38;5;129;01mor\u001B[39;00m _global_backward_pre_hooks \u001B[38;5;129;01mor\u001B[39;00m _global_backward_hooks\n\u001B[0;32m   1746\u001B[0m         \u001B[38;5;129;01mor\u001B[39;00m _global_forward_hooks \u001B[38;5;129;01mor\u001B[39;00m _global_forward_pre_hooks):\n\u001B[1;32m-> 1747\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mforward_call\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m   1749\u001B[0m result \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;01mNone\u001B[39;00m\n\u001B[0;32m   1750\u001B[0m called_always_called_hooks \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mset\u001B[39m()\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torchvision\\models\\resnet.py:155\u001B[0m, in \u001B[0;36mBottleneck.forward\u001B[1;34m(self, x)\u001B[0m\n\u001B[0;32m    152\u001B[0m out \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mrelu(out)\n\u001B[0;32m    154\u001B[0m out \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mconv3(out)\n\u001B[1;32m--> 155\u001B[0m out \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mbn3\u001B[49m\u001B[43m(\u001B[49m\u001B[43mout\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m    157\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mdownsample \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m:\n\u001B[0;32m    158\u001B[0m     identity \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mdownsample(x)\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1736\u001B[0m, in \u001B[0;36mModule._wrapped_call_impl\u001B[1;34m(self, *args, **kwargs)\u001B[0m\n\u001B[0;32m   1734\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_compiled_call_impl(\u001B[38;5;241m*\u001B[39margs, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs)  \u001B[38;5;66;03m# type: ignore[misc]\u001B[39;00m\n\u001B[0;32m   1735\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[1;32m-> 1736\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_call_impl\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1747\u001B[0m, in \u001B[0;36mModule._call_impl\u001B[1;34m(self, *args, **kwargs)\u001B[0m\n\u001B[0;32m   1742\u001B[0m \u001B[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001B[39;00m\n\u001B[0;32m   1743\u001B[0m \u001B[38;5;66;03m# this function, and just call forward.\u001B[39;00m\n\u001B[0;32m   1744\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m (\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_backward_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_backward_pre_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_forward_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_forward_pre_hooks\n\u001B[0;32m   1745\u001B[0m         \u001B[38;5;129;01mor\u001B[39;00m _global_backward_pre_hooks \u001B[38;5;129;01mor\u001B[39;00m _global_backward_hooks\n\u001B[0;32m   1746\u001B[0m         \u001B[38;5;129;01mor\u001B[39;00m _global_forward_hooks \u001B[38;5;129;01mor\u001B[39;00m _global_forward_pre_hooks):\n\u001B[1;32m-> 1747\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mforward_call\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m   1749\u001B[0m result \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;01mNone\u001B[39;00m\n\u001B[0;32m   1750\u001B[0m called_always_called_hooks \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mset\u001B[39m()\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\batchnorm.py:193\u001B[0m, in \u001B[0;36m_BatchNorm.forward\u001B[1;34m(self, input)\u001B[0m\n\u001B[0;32m    186\u001B[0m     bn_training \u001B[38;5;241m=\u001B[39m (\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mrunning_mean \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m) \u001B[38;5;129;01mand\u001B[39;00m (\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mrunning_var \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m)\n\u001B[0;32m    188\u001B[0m \u001B[38;5;250m\u001B[39m\u001B[38;5;124mr\u001B[39m\u001B[38;5;124;03m\"\"\"\u001B[39;00m\n\u001B[0;32m    189\u001B[0m \u001B[38;5;124;03mBuffers are only updated if they are to be tracked and we are in training mode. Thus they only need to be\u001B[39;00m\n\u001B[0;32m    190\u001B[0m \u001B[38;5;124;03mpassed when the update should occur (i.e. in training mode when they are tracked), or when buffer stats are\u001B[39;00m\n\u001B[0;32m    191\u001B[0m \u001B[38;5;124;03mused for normalization (i.e. in eval mode when buffers are not None).\u001B[39;00m\n\u001B[0;32m    192\u001B[0m \u001B[38;5;124;03m\"\"\"\u001B[39;00m\n\u001B[1;32m--> 193\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mF\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mbatch_norm\u001B[49m\u001B[43m(\u001B[49m\n\u001B[0;32m    194\u001B[0m \u001B[43m    \u001B[49m\u001B[38;5;28;43minput\u001B[39;49m\u001B[43m,\u001B[49m\n\u001B[0;32m    195\u001B[0m \u001B[43m    \u001B[49m\u001B[38;5;66;43;03m# If buffers are not to be tracked, ensure that they won't be updated\u001B[39;49;00m\n\u001B[0;32m    196\u001B[0m \u001B[43m    \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mrunning_mean\u001B[49m\n\u001B[0;32m    197\u001B[0m \u001B[43m    \u001B[49m\u001B[38;5;28;43;01mif\u001B[39;49;00m\u001B[43m \u001B[49m\u001B[38;5;129;43;01mnot\u001B[39;49;00m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mtraining\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;129;43;01mor\u001B[39;49;00m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mtrack_running_stats\u001B[49m\n\u001B[0;32m    198\u001B[0m \u001B[43m    \u001B[49m\u001B[38;5;28;43;01melse\u001B[39;49;00m\u001B[43m \u001B[49m\u001B[38;5;28;43;01mNone\u001B[39;49;00m\u001B[43m,\u001B[49m\n\u001B[0;32m    199\u001B[0m \u001B[43m    \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mrunning_var\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43;01mif\u001B[39;49;00m\u001B[43m \u001B[49m\u001B[38;5;129;43;01mnot\u001B[39;49;00m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mtraining\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;129;43;01mor\u001B[39;49;00m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mtrack_running_stats\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43;01melse\u001B[39;49;00m\u001B[43m \u001B[49m\u001B[38;5;28;43;01mNone\u001B[39;49;00m\u001B[43m,\u001B[49m\n\u001B[0;32m    200\u001B[0m \u001B[43m    \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mweight\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m    201\u001B[0m \u001B[43m    \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mbias\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m    202\u001B[0m \u001B[43m    \u001B[49m\u001B[43mbn_training\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m    203\u001B[0m \u001B[43m    \u001B[49m\u001B[43mexponential_average_factor\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m    204\u001B[0m \u001B[43m    \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43meps\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m    205\u001B[0m \u001B[43m\u001B[49m\u001B[43m)\u001B[49m\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\functional.py:2812\u001B[0m, in \u001B[0;36mbatch_norm\u001B[1;34m(input, running_mean, running_var, weight, bias, training, momentum, eps)\u001B[0m\n\u001B[0;32m   2809\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m training:\n\u001B[0;32m   2810\u001B[0m     _verify_batch_size(\u001B[38;5;28minput\u001B[39m\u001B[38;5;241m.\u001B[39msize())\n\u001B[1;32m-> 2812\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mtorch\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mbatch_norm\u001B[49m\u001B[43m(\u001B[49m\n\u001B[0;32m   2813\u001B[0m \u001B[43m    \u001B[49m\u001B[38;5;28;43minput\u001B[39;49m\u001B[43m,\u001B[49m\n\u001B[0;32m   2814\u001B[0m \u001B[43m    \u001B[49m\u001B[43mweight\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   2815\u001B[0m \u001B[43m    \u001B[49m\u001B[43mbias\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   2816\u001B[0m \u001B[43m    \u001B[49m\u001B[43mrunning_mean\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   2817\u001B[0m \u001B[43m    \u001B[49m\u001B[43mrunning_var\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   2818\u001B[0m \u001B[43m    \u001B[49m\u001B[43mtraining\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   2819\u001B[0m \u001B[43m    \u001B[49m\u001B[43mmomentum\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   2820\u001B[0m \u001B[43m    \u001B[49m\u001B[43meps\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   2821\u001B[0m \u001B[43m    \u001B[49m\u001B[43mtorch\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mbackends\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mcudnn\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43menabled\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m   2822\u001B[0m \u001B[43m\u001B[49m\u001B[43m)\u001B[49m\n",
      "\u001B[1;31mKeyboardInterrupt\u001B[0m: "
     ]
    }
   ],
   "execution_count": 30
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T15:00:30.447875Z",
     "start_time": "2025-01-20T15:00:30.447875Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 画图\n",
    "import torch\n",
    "from torchviz import make_dot\n",
    "\n",
    "# Assuming your model is already defined and named 'model'\n",
    "# Construct a dummy input\n",
    "dummy_input = torch.randn(1, 3, 224, 224)  # Replace with your input shape\n",
    "\n",
    "# Forward pass to generate the computation graph\n",
    "output = model(dummy_input)\n",
    "\n",
    "# Visualize the model architecture\n",
    "dot = make_dot(output, params=dict(model.named_parameters()))\n",
    "dot.render(\"model_architecture\", format=\"png\")  # Save the visualization as an image"
   ],
   "id": "e47e6ab611ddb7c5",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T15:00:30.448877Z",
     "start_time": "2025-01-20T15:00:30.448877Z"
    }
   },
   "cell_type": "code",
   "source": [
    "def plot_record_curves(record_dict, sample_step=500):\n",
    "    # .set_index(\"step\") 将 step 列设置为 DataFrame 的索引\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    last_step = train_df.index[-1]  # 最后一步的步数\n",
    "\n",
    "    print(train_df)\n",
    "    print(val_df)\n",
    "\n",
    "    # 画图 \n",
    "    fig_num = len(train_df.columns)  # 画两张图,分别是损失和准确率\n",
    "\n",
    "    # plt.subplots：用于创建一个包含多个子图的图形窗口。\n",
    "    # 1：表示子图的行数为 1。\n",
    "    # fig_num：表示子图的列数，即子图的数量。\n",
    "    # figsize=(5 * fig_num, 5)：设置整个图形窗口的大小，宽度为 5 * fig_num，高度为 5。\n",
    "    # fig：返回的图形对象（Figure），用于操作整个图形窗口。\n",
    "    # axs：返回的子图对象（Axes 或 Axes 数组），用于操作每个子图。\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        # train_df.index 是 x 轴数据（通常是 step）。\n",
    "        # train_df[item] 是 y 轴数据（当前指标的值）。\n",
    "        axs[idx].plot(train_df.index, train_df[item], label=\"train:\" + item)\n",
    "        # val_df.index 是 x 轴数据。\n",
    "        # val_df[item] 是 y 轴数据。\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=\"val:\" + item)\n",
    "        axs[idx].grid()  # 显示网格\n",
    "        axs[idx].legend()  # 显示图例\n",
    "        axs[idx].set_xticks(range(0, train_df.index[-1]+1, 5000))  # 设置x轴刻度\n",
    "        axs[idx].set_xticklabels(map(lambda x: f\"{x // 1000}k\", range(0, last_step + 1, 5000)))  # 设置x轴标签\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "plot_record_curves(record_dict)"
   ],
   "id": "e154e72d9a6c6371",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 评估",
   "id": "f2d1830a8d4d472e"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "model = ResNet50(num_classes=len(train_ds.classes))  #上线时加载模型\n",
    "model = model.to(device)  # 将模型移到GPU上\n",
    "\n",
    "# 加载最好的模型\n",
    "# torch.load：加载保存的模型权重或整个模型。\n",
    "# \"checkpoints/best.ckpt\"：模型权重文件路径。\n",
    "# weights_only=True：仅加载模型的权重，而不是整个模型（包括结构和参数）。这是 PyTorch 2.1 引入的新特性，用于增强安全性。\n",
    "# map_location=device：将模型加载到当前设备（GPU或CPU）。\n",
    "model.load_state_dict(torch.load(\"checkpoints/05_monkey_resnet50_best.ckpt\", weights_only=True, map_location=device))  # 加载最好的模型\n",
    "\n",
    "model.eval()  # 评估模式\n",
    "loss, acc = evaluate(model, val_loader, loss_fct)\n",
    "print(f\"Test loss: {loss:.4f}, Test acc: {acc:.4f}\")"
   ],
   "id": "878f92dafc460021",
   "outputs": [],
   "execution_count": null
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
